package cn.remex.task;

import cn.remex.core.RemexStartupEvent;
import cn.remex.core.RemexStartupListener;
import cn.remex.core.exception.ServiceCode;
import cn.remex.core.quartz.Job;
import cn.remex.core.quartz.SchedulerManager;
import cn.remex.core.quartz.Task;
import cn.remex.core.util.Assert;
import cn.remex.core.util.Judgment;
import cn.remex.core.util.SystemHelper;
import cn.remex.db.Database;
import cn.remex.task.model.SysMatter;
import cn.remex.task.model.SysTask;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.remex.db.sql.WhereRuleOper.*;
import static cn.remex.task.TaskConst.ProcessStatus.*;

/**
 * 名称：
 * 缩写：
 * 用途：核心控制本应用内信审服务调度的工具类。<br>
 *
 * 此处主要用户就是控制分析器的启动，停止，状态检查及恢复。<br>
 *
 * Created by yangy on 2016/12/23 0023.
 */

public class TaskCenter extends RemexStartupListener implements TaskConst {
	public static final int timeout = 60 * 1000;//1分钟就算心跳死了
	public static final int maxAnalyzerCount=100;
	public static String machineNode=SystemHelper.getLocalIP();
	private static Task TaskCenterDaemon = new Task(TaskCenterDaemon.class, "0/10 * * * * ?").delayStart(6).ignoreWhenExsite(true);
	private static Map<String, TaskController> curCreditAnalyzers = new HashMap<>();

	//============支持的分析器的job列表配置，暂时放到这里，后面放到配置文件或数据库中=================================//
	private static Map<String,Class<? extends TaskController>> TaskTypeClass =new HashMap<>();
	private static Map<String,Map<String,Class<? extends Job>>> AnalyzerJobMap =new HashMap<>();

	private static String[] CanSetDoingJobTypes = new String[]{"NOTHING"};
//	static {
//		//订单的分析任务列表
//		Map<String,Class<? extends Job>> OrderAnalysisJobMap =new HashMap<>();
//		OrderAnalysisJobMap.put(String.GRAB_JXL_RECORD,JuXinLiRecordGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_JXL_REPORT,JuXinLiReportGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_BAIRONG,BaiRongGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_LAKALA, LakalaGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_SHUJUTANG, ShuJuTangGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_TONGDUN,TongDunGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_ALICOURT,AliCourtGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_SY_RECORD,WeCashRecordGrabJob.class);
//		OrderAnalysisJobMap.put(String.GRAB_SY_REPORT,WeCashReportGrabJob.class);
//
//		OrderAnalysisJobMap.put(String.ANA_Report,OrderReportAnaJob.class);
//		AnalyzerJobMap.put(CA_InstOrder, OrderAnalysisJobMap);
//	}

	public void setTaskTypeClass(Map<String,Class<? extends TaskController>> typeClassMap){
		TaskTypeClass = typeClassMap;
	}

	public void setCanSetDoingJobTypes(List<String> jobTypes){
		if(jobTypes != null && jobTypes.size() > 0) {
			CanSetDoingJobTypes = jobTypes.toArray(new String[]{});
		}
	}

//	public static void registerTaskType(String typeName, Class<? extends TaskController> clazz){
//		Assert.notNullAndEmpty(typeName, ServiceCode.FAIL, "任务类型不能为空");
//		Assert.isTrue(!TaskTypeClass.containsKey(typeName), ServiceCode.FAIL, "任务类型不能重复");
//
//		TaskTypeClass.put(typeName, clazz);
//	}

	public static Class<? extends Job> obtainMatterJobClass(String analyzerType, String jobType) {
		Assert.isTrue(AnalyzerJobMap.containsKey(analyzerType), ServiceCode.FAIL, "不存在对应类型的分析器, analyzerType=" + analyzerType);
		return AnalyzerJobMap.get(analyzerType).get(jobType);
	}


	//======================分析器的操作函数=======================//
	public static TaskController obtainCurTaskControllers(SysTask curAnaConfig) {
		return curCreditAnalyzers.get(curAnaConfig.key());
	}
	public static TaskController upsertTaskController(String analyzerType, String relationId, String analyzerJobParams) {
		//必须先查一下库，才能实现update or insert逻辑合并。
		SysTask cac =  Database.select(SysTask.class)
				.filterBy(SysTask::getRelationId, equal, relationId)
				.filterBy(SysTask::getTaskType, equal, analyzerType)
				.execute().obtainBean();
		//创建分析器会根据逻辑保存好分析器的相关配置
		TaskController analyzer = createTaskController(null!=cac?cac:new SysTask(analyzerType, relationId, analyzerJobParams));
		return analyzer;
	}
	public static TaskController createTaskController(SysTask _curAnaConfig) {
		//1----------------处理分析器配置参数-----------------//
		//如果分析器配置没有保存，则去检查是否已经存在该分析器，否则直接使用该配置
		//主要场景：1用户通知我们要创建一个分析器；2CreditCenter通知我们要创建分析。前者配置参数不全，后者是有全的参数的。
		boolean newConf = _curAnaConfig.modelIsNew();
		SysTask curSysTask =
				newConf ?
						null == (curSysTask = Database.select(SysTask.class)
								.filterBy(SysTask::getRelationId, equal, _curAnaConfig.getRelationId())
								.filterBy(SysTask::getTaskType, equal, _curAnaConfig.getTaskType())
								.execute().assertTrue(rvo -> rvo.getRecordCount() == 0, ServiceCode.FAIL, "分析器已经存在, relationId=" + _curAnaConfig.getRelationId())
								.obtainBean()) ? _curAnaConfig : curSysTask
						:
						_curAnaConfig;//查询该订单分析器是否存在
		//如果不是数据库中保存的分析器配置，则保存分析器配置并添加所属Job配置
		if(newConf){
			logger.info("创建新分析器配置, relationId={}, analyzerType={}, analyzerJobParams={}", curSysTask.getRelationId(), curSysTask.getRelationType(), curSysTask.getParams());
			curSysTask.setProcessStatus(ProcessStatus.TODO);
			Database.store(curSysTask).execute().assertEffectOneRow(ServiceCode.FAIL, "分析器添加失败");


			//------添加分析器下属的各种job----//
			Map<String, Class<? extends Job>> curJobMap = AnalyzerJobMap.get(curSysTask.getTaskType());
			//将应该有的job放到数据库中
			SysTask finalSysTask = curSysTask;
			curJobMap.forEach((type, jobClass) -> {
				SysMatter creditJobConfig = new SysMatter();
				creditJobConfig.setMatterType(type);
				creditJobConfig.setSysTask(finalSysTask);
				creditJobConfig.setProcessStatus(ProcessStatus.NEW);
				Database.insert(creditJobConfig).execute();
			});

		}

		//2----------------创建分析器-----------------//
		TaskController taskController;
		Class<? extends TaskController> curTaskControllerClass = TaskTypeClass.get(curSysTask.getTaskType());
		if (null != curTaskControllerClass) {
			try {
				taskController = curTaskControllerClass.getDeclaredConstructor(SysTask.class).newInstance(curSysTask);
			} catch (Exception e) {
				throw new TaskException(ServiceCode.FAIL, "无法创建对应的任务控制器");
			}
		}else {
			logger.error("目前仅支持订单信审报告分析，当前type=", curSysTask.getRelationType());
			throw new TaskException(ServiceCode.ERROR, "目前仅支持订单信审报告分析");
		}

		//3----------------更新分析器参数-----------------//
		if (!Judgment.nullOrBlank(curSysTask.getParams())) {
			//系统默认必须更新的
			if(!newConf)//新的不用更新，再创建的时候已经保存过
				Database.update(SysTask.class)
						.filterBy(SysTask::getRelationId, equal, curSysTask.getRelationId())
						.filterBy(SysTask::getTaskType, equal, curSysTask.getTaskType())
						.assignColumn(SysTask::getParams, curSysTask.getParams())
						.execute().assertEffectOneRow(ServiceCode.FAIL, "更新分析器失败, relationId=" + curSysTask.getRelationId());
			//调用分析器自己处理参数的功能
			taskController.updateParams(curSysTask.getParams());
		}

		return taskController;
	}

	//boot and stop 才会放到当前节点
	public static TaskController bootTaskController(SysTask curAnaConfig) {
		//通过BusinessService调用来实现事务控制，确保服务启动成功和数据状态一致。
		logger.info("启动分析器, analyzerType={}, relationId={}", curAnaConfig.getTaskType(), curAnaConfig.getRelationId());

		TaskController taskController = createTaskController(curAnaConfig);


		//更新数据库状态，获取*句柄
		int updateAna = Database.update(SysTask.class)
				.assignColumn(SysTask::getMachineNode, machineNode)
				.assignColumn(SysTask::getProcessStatus, DOING)

				.filterById(curAnaConfig.getId())
				.execute().assertTrue(rvo -> rvo.getEffectRowCount() <= 1, ServiceCode.FAIL, "分析器启动错误, relationId: " + curAnaConfig.getRelationId())
				.getEffectRowCount();
		if(updateAna==0){
			//没有更新成功获取其他人已经启动，或者分析器状态已经发生改变，则不用启动。
			return null;
		}

		taskController.boot();
		curCreditAnalyzers.put(taskController.getSysTask().key(), taskController);


		return taskController;
	}

	/**
	 * 停止任务，但不移除curCreditAnalyzers的元素，否则在foreach的时候，会出现问题
	 *
	 * @param key
	 */
	public static void stopTaskController(String key){
		Assert.notNull(curCreditAnalyzers.get(key).getCurQuartzTask(), ServiceCode.FAIL, "分析器对应的日程task为空, 分析器key=" + key);
		TaskController config = curCreditAnalyzers.get(key);


		SchedulerManager.getDefaultScheduler().deleteTask(config.getCurQuartzTask());//停止分析器
	}

	//======================分析器下属job调度功能函数=======================//
	public static void updateMatter(SysMatter jobConfig, String... needUpdateMatterTypes) {
		Database.update(SysMatter.class)
				.filterBy(SysMatter::getSysTask, equal, jobConfig.getSysTask())
				.filterBy(SysMatter::getProcessStatus, notIn, DOING)//只要不是运行中的，都可以接到TODO的通知
				.filterBy(SysMatter::getMatterType, in, needUpdateMatterTypes)
				.assignColumn(SysMatter::getProcessStatus, TODO)
				.execute();
	}
	public static boolean bootMatter(SysMatter jobConfig) {
		return Database.update(SysMatter.class)
				.filterBy(SysMatter::getSysTask, equal, jobConfig.getSysTask())
				.filterBy(SysMatter::getProcessStatus, notIn, DOING)//只要不是运行中的，都可以接到TODO的通知
				.filterBy(SysMatter::getMatterType, in, jobConfig.getMatterType())
				.assignColumn(SysMatter::getProcessStatus, TODO)
				.execute().assertTrue(rvo -> rvo.getEffectRowCount() < 1, ServiceCode.FAIL, "启动分析工作发生服务异常").getEffectRowCount()==1;
	}


	//======================job调度功能函数=======================//
	//检查当前所有正在运行的分析器，移除无用的分析器
	public static void checkAndRemoveController(Date curHeartbeatTime) {
		List<String> toRemovedKeys = new ArrayList<>();
		for (String key : curCreditAnalyzers.keySet()) {
			TaskController curAnalyzer = curCreditAnalyzers.get(key);
			ProcessStatus curStatus = curAnalyzer.getCurStatus();
			SysTask curAnalyzerConfig = curAnalyzer.getSysTask();
			boolean checkTimeout = curAnalyzerConfig.checkTimeout(curHeartbeatTime);
			// 失败或完成或错误或等待中需要移除
			// 不在quartz队列中的需要删除
			// 如果已经超时也需要移除
			if (FINISH.equals(curStatus) || ERROR.equals(curStatus) || WAITING.equals(curStatus) || FAIL.equals(curStatus)
					|| !SchedulerManager.getDefaultScheduler().checkExists(TaskCenterDaemon)
					|| curAnalyzer.getSysTask().checkTimeout(curHeartbeatTime)
					) {
				logger.info("移除分析器，key={}, status={}，checkTimeout={}", key, curStatus, checkTimeout);
				TaskCenter.stopTaskController(key);

				toRemovedKeys.add(key);
			}
		}

		toRemovedKeys.forEach(key -> curCreditAnalyzers.remove(key));
	}
	//检查当前所有排队的分析器，启动需要启动的分析器
	public static void checkAndBootController(Date curHeartbeatTime) {
		List<SysTask> preparedAnalyzers = Database.select(SysTask.class)
//				.filterBy(SysTask::getMachineNode, in, machineNode, NOT_RUNNING) //分页获取可以run的分析器到处理队列中，其他manchine的也会拿到，根据心跳时间来判断是否假死
				.filterBy(SysTask::getProcessStatus, in, NEW, TODO, DOING) //WAITING LHY 暂时不能启动WAITING的分析器 //FAIL, LHY LXT 失败的暂时不要调用，容易循环
				.withBase().rowCount(maxAnalyzerCount*2).execute().obtainObjects(SysTask.class);//多查一些出来，面得有空闲的

		//检查是否存在假死的分析任务
		//检查是否当前进行中的分析任务列表状态是否正常，检查修正相关的状态，必要的时候同步到数据中
		//先获取任务的句柄，通过数据的machineNode & HeartbeatTime。
		ArrayList<String> livedAnalyzerIds = new ArrayList<>();
		//如果队列数量有富裕，则新启动对应的分析任务
		//当条件符合时
		//1.数据库状态更新正常
		//2.运行数量符合要求
		for (SysTask curAnalyzerConfig : preparedAnalyzers) {
			//如果已经在运行列表中且已经运行了，则不用启动，单需要更新数据库中的 HeartbeatTime 字段，代表本线程还活着，否则判断是假死
			if (null != curCreditAnalyzers.get(curAnalyzerConfig.key())
					&& SchedulerManager.getDefaultScheduler().checkExists(curCreditAnalyzers.get(curAnalyzerConfig.key()).getCurQuartzTask())
					) {
				curAnalyzerConfig.setHeartbeatTime(curHeartbeatTime);
				livedAnalyzerIds.add(curAnalyzerConfig.getId());
				continue;
			}

			//如果数据中强制被其他节点抢走，则也从本节点中移除:节点和现在的不同，且没有超时,且当前列表有
			if(!machineNode.equals(curAnalyzerConfig.getMachineNode())
					&& !curAnalyzerConfig.checkTimeout(curHeartbeatTime)
					&& curCreditAnalyzers.containsKey(curAnalyzerConfig.key())){
				stopTaskController(curAnalyzerConfig.key());
				curCreditAnalyzers.remove(curAnalyzerConfig.key());
			}

			//如果还可以继续运行则启动
			if (curCreditAnalyzers.size() < maxAnalyzerCount) {
				//1.如果是本节点的分析器，可以启动
				//2.如果是其他节点但是超时了，也可以启动
				if(machineNode.equals(curAnalyzerConfig.getMachineNode())
						|| curAnalyzerConfig.checkTimeout(curHeartbeatTime)){
					logger.info("启动分析器，key={}, checkTimeout={}", curAnalyzerConfig.key(),curAnalyzerConfig.checkTimeout(curHeartbeatTime));
					bootTaskController(curAnalyzerConfig); //此行不能跑出任何异常，否则影响心跳，导致所有job会被别人抢走
					curAnalyzerConfig.setHeartbeatTime(curHeartbeatTime);//数据库的心跳再下面的update中更新，此处更新内存的心跳标志，因为守护线程是根据内存的值判断是否假死的，config.checkTimeout();
					livedAnalyzerIds.add(curAnalyzerConfig.getId());
				}
				// continue;
			}
		}

		//心跳更新，此处没有事务，一定不能更新错误，否则状态就乱了。
		if(livedAnalyzerIds.size()>0)
			Database.update(SysTask.class)
					.assignColumn(SysTask::getHeartbeatTime, curHeartbeatTime)
					.assignColumn(SysTask::getMachineNode, machineNode)
					.filterBy(SysTask::getId, in, livedAnalyzerIds.toArray())
					.execute();
	}

	//	检查所有WAITING的任务，若expectStartTime已经到达，则把任务状态改为DOING
	public static void checkAndSetDoingController(Date curHeartbeatTime) {
		List<SysTask> needDoingAnalyzers = Database.select(SysTask.class).withBase()
				.filterBy(SysTask::getProcessStatus, equal, WAITING)
				.filterBy(SysTask::getExpectStartTime, le, curHeartbeatTime)
				.filterBy(SysTask::getTaskType, in, CanSetDoingJobTypes)
				.execute().obtainBeans();

		if(needDoingAnalyzers != null && needDoingAnalyzers.size() > 0){
			needDoingAnalyzers.forEach(job -> {
				Database.update(SysTask.class)
						.assignColumn(SysTask::getProcessStatus, DOING)
						.assignColumn(SysTask::getHeartbeatTime, curHeartbeatTime)
						.filterById(job.getId())
						.execute();
			});
		}
	}

	//======================核心线程启动=======================//
	public static void stopCenter(){
		logger.info("mini任务平台核心守护服务停止中...");
		SchedulerManager.getDefaultScheduler().deleteTask(TaskCenterDaemon);
		logger.info("mini任务平台核心守护服务停止");
	}
	public static void startCenter(){
		logger.info("mini任务平台核心守护服务启动中");
		SchedulerManager.getDefaultScheduler().start();
		SchedulerManager.getDefaultScheduler().addTask(TaskCenterDaemon);
		logger.info("mini任务平台核心守护服务启动完成");
	}


	@Override
	public void onRemexStartupEvent(RemexStartupEvent paramApplicationEvent) {
		logger.info("mini任务平台自动启动中");
		String runnerConfig = System.getenv("MDWL_JOB_RUNNER");
		if("1".equals(runnerConfig)) {
			startCenter();
		}
		logger.info("mini任务平台自动启动完成");
	}
}
